/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;

/**
 *
 * @author Dung Pham
 */
public class Message {

    /**
     * do dai qui dinh cho 1 header cua message<br> 1 byte cho command, 2 byte
     * cho length data.
     */
    public static final byte HEADER_LENGTH = 3;
    /**
     * 2 byte cho cmd, 4 byte cho length data.
     */
    public static final byte HEADER_LENGTH_PROTOCOL_10 = 4;
    public static final byte NEW_HEADER_LENGTH = 2;
    public static final int MAX_MESSAGE_LENGTH = Short.MAX_VALUE - HEADER_LENGTH;
    public static final int MAX_MESSAGE_LENGTH_PROTOCOL_10 = Integer.MAX_VALUE - HEADER_LENGTH_PROTOCOL_10;
    private WeakReference<Session> session;
    //private Session session;
    public final int command;
    private ByteArrayOutputStream os = null;
    public DataOutputStream dos = null;
    private ByteArrayInputStream is = null;
    public DataInputStream dis = null;
    private byte[] bytes = null;
    private byte[] newBytes = null;
    private byte[] newBytesProtocol10 = null;
    private byte[] messageData = null;
    private byte encryptStatus;

    public Message(int command) {
        this(command, true, null);
    }

    /**
     * Them status cho protocol 10.
     *
     * @param command cmd id
     * @param encryptStatus Bien nay quyet dinh du lieu cua message co can phai
     * encrypt hay khong.
     */
    public Message(int command, boolean isEncrypt, Session ioSession) {
        this.command = command;
        if (ioSession != null) {
            this.session = new WeakReference<Session>(ioSession);
            //this.session = ioSession;
        }

        os = new ByteArrayOutputStream();
        dos = new DataOutputStream(os);
        if (isEncrypt) {
            encryptStatus = 1;
        } else {
            encryptStatus = 0;
        }
    }

    public Message(int command, Session ioSession) {
        this(command, true, ioSession);
    }

    public Message(int command, byte[] data) {
        //this.type = type;
        this.command = command;
        is = new ByteArrayInputStream(data);
        dis = new DataInputStream(is);
        messageData = data.clone();
    }

    public byte[] toByteArray() {
        int datalen = 0;
        byte[] data = null;
        if (bytes == null) {
            try {
                if (dos != null) {
                    dos.flush();
                    data = os.toByteArray();
                    datalen = data.length;
                    dos.close();
                }
                ByteArrayOutputStream bos1 = new ByteArrayOutputStream(HEADER_LENGTH + datalen);
                DataOutputStream dos1 = new DataOutputStream(bos1);
                //dos1.writeByte(type);
                dos1.writeInt(command);
                dos1.writeShort(datalen);
                if (datalen > 0) {
                    dos1.write(data);
                }
                bytes = bos1.toByteArray();
                dos1.close();
            } catch (IOException e) {
                //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error(null, e);
               
            }
        }
        return bytes;
    }

    public byte[] toByteArray(Tea tea) {
        int datalen = 0;
        byte[] data = null;
        // cache lai data neu chua cache
        if (bytes == null) {
            try {
                if (dos != null) {
                    dos.flush();
                    data = os.toByteArray();
                    datalen = data.length;
                    dos.close();
                }
                ByteArrayOutputStream bos1 = new ByteArrayOutputStream(HEADER_LENGTH + datalen);
                DataOutputStream dos1 = new DataOutputStream(bos1);
                //dos1.writeByte(type);
                dos1.writeInt(command);
                dos1.writeShort(datalen);
                if (datalen > 0) {
                    dos1.write(data);
                }
                bytes = bos1.toByteArray();
                dos1.close();
            } catch (IOException e) {
                //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error(null, e);
                
            }
        }
        // if message have extra data
        // start encrypt data
        if (bytes.length > HEADER_LENGTH) {
            // copy extra data
            data = new byte[bytes.length - HEADER_LENGTH];
            for (int i = 0; i < data.length; i++) {
                data[i] = bytes[i + HEADER_LENGTH];
            }
            // encrypt extra data
            byte[] byteEncrypt = tea.encrypt(data);
            ByteArrayOutputStream baos1 = new ByteArrayOutputStream(HEADER_LENGTH + byteEncrypt.length);
            DataOutputStream dos1 = new DataOutputStream(baos1);
            try {
                // make new encrypt message
                dos1.writeInt(command);
                dos1.writeShort(byteEncrypt.length);
                dos1.write(byteEncrypt);
                byte[] returnBytes = baos1.toByteArray();
                dos1.close();
                return returnBytes;
            } catch (IOException ex) {
                //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error("error on encrypt message", ex);
               // Reporter.error("toByteArray with encrypt got exception :", ex);
            }
        }
        return bytes;
    }

    /**
     * User for new protocol
     *
     * @param tea
     * @return
     */
    public byte[] toByteArrayEx(Tea tea) {
        int datalen = 0;
        byte[] data = null;
        // cache lai data neu chua cache
        if (newBytes == null) {
            try {
                if (dos != null) {
                    dos.flush();
                    data = os.toByteArray();
                    datalen = data.length;
                    dos.close();
                }
                ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
                DataOutputStream dos1 = new DataOutputStream(bos1);
                dos1.writeInt(command);
                if (datalen > 0) {
                    dos1.write(data);
                }
                newBytes = bos1.toByteArray();
                dos1.close();
            } catch (IOException e) {
                //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error(null, e);
               // Reporter.error("toByteArrayEx with encrypt got exception :", e);
            }
        }
        // if message have extra data
        // start encrypt data
        byte[] byteEncrypt = tea.encrypt(newBytes);
        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        DataOutputStream dos1 = new DataOutputStream(baos1);
        try {
            // make new encrypt message
            dos1.writeShort(byteEncrypt.length);
            dos1.write(byteEncrypt);
            byte[] returnBytes = baos1.toByteArray();
            dos1.close();
            return returnBytes;
        } catch (IOException ex) {
            //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error("error on encrypt message", ex);
           // Reporter.error("toByteArrayEx with encrypt got exception :", ex);
        }
        return newBytes;
    }

    /**
     * User for new protocol with router client protocol.
     *
     * @return
     */
    public byte[] toByteArrayEx() {
        int datalen = 0;
        byte[] data = null;
        // cache lai data neu chua cache
        if (newBytes == null) {
            try {
                if (dos != null) {
                    dos.flush();
                    data = os.toByteArray();
                    datalen = data.length;
                    dos.close();
                }
                ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
                DataOutputStream dos1 = new DataOutputStream(bos1);
                dos1.writeInt(command);
                if (datalen > 0) {
                    dos1.write(data);
                }
                newBytes = bos1.toByteArray();
                dos1.close();
            } catch (IOException e) {
               // Reporter.error("toByteArrayEx got exception :", e);
            }
        }

        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        DataOutputStream dos1 = new DataOutputStream(baos1);
        try {
            dos1.writeShort(newBytes.length);
            dos1.write(newBytes);
            byte[] returnBytes = baos1.toByteArray();
            dos1.close();
            return returnBytes;
        } catch (IOException ex) {
           // Reporter.error("toByteArrayEx got exception :", ex);
        }
        return bytes;
    }

    /**
     * User for new protocol
     *
     * @param tea
     * @return
     */
    public byte[] toByteArrayProtocol10(Tea tea) {
        int datalen = 0;
        byte[] data = null;
        // cache lai data neu chua cache
        if (newBytesProtocol10 == null) {
            try {
                if (dos != null) {
                    dos.flush();
                    data = os.toByteArray();
                    datalen = data.length;
                    dos.close();
                }
                ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
                DataOutputStream dos1 = new DataOutputStream(bos1);
                dos1.writeInt(command);
                if (datalen > 0) {
                    dos1.write(data);
                }
                newBytesProtocol10 = bos1.toByteArray();
                dos1.close();
            } catch (IOException e) {
                //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error(null, e);
               // Reporter.error("toByteArrayEx with encrypt got exception :", e);
            }
        }
        // if message have extra data
        // start encrypt data
        byte[] byteEncrypt;
        if (encryptStatus == 1) {
            byteEncrypt = tea.encrypt(newBytesProtocol10);
        } else {
            byteEncrypt = newBytesProtocol10;
        }
        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        DataOutputStream dos1 = new DataOutputStream(baos1);
        try {
            // make new encrypt message protocol10 lenght is int
            dos1.writeInt(byteEncrypt.length + 1);
            dos1.write(encryptStatus);
            dos1.write(byteEncrypt);
            byte[] returnBytes = baos1.toByteArray();
            dos1.close();
            return returnBytes;
        } catch (IOException ex) {
            //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error("error on encrypt message", ex);
           // Reporter.error("toByteArrayEx with encrypt got exception :", ex);
        }
        return newBytesProtocol10;
    }

    public Session getSession() {
        if (session != null) {
            return session.get();
            //return session;
        }

        return null;
    }

    public void setSession(Session ioSession) {
        this.session = new WeakReference<Session>(ioSession);
        //this.session = ioSession;
    }

    public byte[] getMessageData() {
        return messageData.clone();
    }

    public void cleanup() {
        try {
            if (dis != null) {
                dis.close();
            }
            if (dos != null) {
                dos.close();
            }

            bytes = null;
            newBytes = null;
            this.session = null;
        } catch (IOException e) {
            //Logger.getLogger(IwinLoggerNames.ERROR_DB_LOG_NAME).error(null, e);
           // Reporter.error("Message cleanUp got exception :", e);
        }
    }

    /**
     * Lay gia tri protocol 10 cmd.
     *
     * @return
     */
//    public int getCommand10() {
//        return command / MessageCommandProtocol10.MAX_SUB_CMD_PRO_10;
//    }

    @Override
    public String toString() {
        return "Message{" + "command=" + command + '}';
    }
}
